home *** CD-ROM | disk | FTP | other *** search
/ Amiga Format CD 35 / Amiga Format AFCD35 (Issue 119, Jan 1999).iso / -seriously_amiga- / misc / poolmem / developer.readme next >
Text File  |  1998-11-09  |  22KB  |  552 lines

  1. Abstract:
  2.  
  3. This brief document describes how to allocate and deallocate memory correctly,
  4. i.e. in a way compatible to the Os (and, as a result, compatible to PoolMem).
  5. ______________________________________________________________________________
  6.  
  7. The following rules apply to all programs that are supposed to run in an OS
  8. friendly way. I didn't make them up myself. What you find here is more or 
  9. less a copy of the rules taken from the ROM Kernal reference manual, the 
  10. official Amiga developer documentation.
  11.  
  12. Breaking these rules will result in unstable programs, with or without
  13. any additional memory tools. A program that seems to run fine without 
  14. PoolMem, but crashes with PoolMem is, nevertheless, unstable and might crash
  15. in certain situations, even without this tool.
  16.  
  17.  
  18. Allocation of memory:
  19.  
  20. o)    The MEMF_PUBLIC bit:
  21.     Set the MEMF_PUBLIC bit (exec/memory.h). You usually want it!
  22.  
  23.     NOT setting this bit results in memory that is 
  24.     a) *private* to your task, i.e. can't be read from any other task
  25.     b) and can't be read safely within a Forbid()/Permit() or
  26.        Enable()/Disable() pair.
  27.  
  28.     The current Os DOES NOT implement any checks for this rule, neither
  29.     does PoolMem. However, future memory managers might see this bit as
  30.     a hint to assign "virtual memory" to the allocation, i.e. memory that
  31.     can be swapped out to disk.
  32.  
  33.     As an example, VMM requires correct usage of this bit.
  34.  
  35.     All data that is supposed to hold Os structures MUST BE ALLOCATED 
  36.     WITH THE MEMF_PUBLIC flag set, any memory that is passed to other
  37.     tasks, interrupts, exceptions, I/O buffers MUST BE ALLOCATED WITH 
  38.     THIS BIT SET.
  39.  
  40.     The only exception are private structures that are only read
  41.     or written to by your task, that are never passed nor read or 
  42.     written to by other processes or Os functions and that are not 
  43.     accessed with multitasking disabled.
  44.  
  45. o)     Memory flushes:
  46.     Be prepared that a memory allocation might flush unused libraries, 
  47.     fonts and devices from memory. In special, DO NOT USE CLOSED 
  48.     RESORCES. Using a "FindName()" on the exec resource lists IS NOT
  49.     ENOUGH to use a resource.
  50.  
  51.     If you DO NOT want that resources get flushed, set the 
  52.     MEMF_NO_EXPUNGE flag as memory attribute. See exec/memory.h.
  53.  
  54. o)    Memory and custom chips:
  55.     Memory that should be read by the Amiga custom chip set MUST BE
  56.     ALLOCATED with the MEMF_CHIP attribute set or the custom chips 
  57.     won't be able to address this memory. That goes for:
  58.  
  59.     o) display buffers (native bitmaps)
  60.     o) hardware copper lists (but not their gfx abstractions for the
  61.                   CMove(),CWait() etc... family)
  62.     o) image bitmaps (struct IntuiImage->ImageData)
  63.     o) floppy hardware buffers (but since V37 not required for the
  64.                     trackdisk.device I/O buffer)
  65.     o) hardware audio buffers
  66.     o) hardware sprites and image datas of "Bobs"
  67.     o) everything else the custom chip set might access    
  68.  
  69. o)    Order of memory blocks:
  70.     Do not make *any* assumptions about the order in which you get
  71.     memory. The second allocation is not necessarely the higher
  72.     address!
  73.  
  74. o)    The MEMF_FAST bit:
  75.     Do not use the MEMF_FAST bit unnecessary if chip mem would be
  76.     O.K. for you, too. The operating system is smart enough to
  77.     allocate fast memory for you if that is available. It will fall
  78.     back to chip mem if fast mem is not available. There's usually
  79.     no reason to ask for fast mem explicitly.
  80.  
  81. o)    Alignment:
  82.     It is guaranteed that all memory allocated by AllocMem() is aligned
  83.     to two long word boundaries, i.e. the bits 0 to 2 of the address will
  84.     always be zero. NOT MORE! If you need more alignment, see the kludge
  85.     below.
  86.  
  87. o)    Size of buffers:
  88.     Make sure you allocate enough memory even for the worst case. A
  89.     C style string needs n+1 bytes memory to hold a string of length n.
  90.     Some Os functions require, due to bugs, a slightly larger buffer
  91.     than you might think, check the "BUGS" section of the autodocs.
  92.     (Mostly dos functions suffer from this bug, but some intuition
  93.      functions require this as well).
  94.  
  95. o)    Memory attributes:
  96.     Do NOT set ANY undocumented bits for the memory attributes of
  97.     AllocMem(). They *might* be ignored for this version of the Os,
  98.     but probably won't the next version. Check the exec/memory.h
  99.     file for valid flags. As for the current (V40) version of the Os,
  100.     the following flags have been defined:
  101.  
  102. #define MEMF_ANY    (0L)        /* Any type of memory will do */
  103. #define MEMF_PUBLIC (1L<<0)    /* Damn important, see caveats above !*/
  104. #define MEMF_CHIP   (1L<<1)    /* for custom chips */
  105. #define MEMF_FAST   (1L<<2)    /* explicitly fast mem, see caveats! */
  106. #define MEMF_LOCAL  (1L<<8)     /* Memory that does not go away at RESET */
  107. #define MEMF_24BITDMA (1L<<9)   /* DMAable memory within 24 bits of address */
  108. #define MEMF_KICK   (1L<<10)    /* Memory that can be used for KickTags */
  109.  
  110. #define MEMF_CLEAR   (1L<<16)   /* AllocMem: NULL out area before return */
  111. #define MEMF_LARGEST (1L<<17)   /* AvailMem: return the largest chunk size */
  112. #define MEMF_REVERSE (1L<<18)   /* AllocMem: allocate from the top down */
  113. #define MEMF_TOTAL   (1L<<19)   /* AvailMem: return total size of memory */
  114.  
  115. o)    Memory contents:
  116.     Do NOT MAKE any asumption about the contents of the memory block
  117.     unless you specified the MEMF_CLEAR attribute to erase the memory
  118.     block. Not setting this bit is a bit faster, but results in a 
  119.     memory block with whatever contents you might dream of.
  120.  
  121. o)    Self modifying code:
  122.     Self modifying code should be avoided. 
  123.     (What do thing this is? A C64? :-)
  124.     If you absolutely MUST play with this and can't go 'round this,
  125.     use the following Os call to flush the CPU caches once you've
  126.     placed your code in memory and need to run it:
  127.  
  128.     ClearCacheU()
  129.  
  130.     Do NOT expect that it is there BEFORE you called this routine.
  131.     This is even more important to routines like interrupts that are
  132.     called asynchroniously.
  133.  
  134. o)    Failures:
  135.     Feel prepared that your memory request might fail. An explicit
  136.     check is REQUIRED after an AllocMem() call. Just "going guru" in
  137.     this case *IS NOT ENOUGH*. Print a warning message, abort your
  138.     program safely, CHECK YOUR CODE!
  139.  
  140.     Assembly language authors: NO, IT'S NOT DOCUMENTED THAT AllocMem()
  141.     SETS THE ZERO BIT IF THE ALLOCATION FAILED. YOU'VE TO TEST THAT
  142.     YOURSELF.
  143.  
  144.     If your calling task is indeed a process, OS versions V37 and
  145.     above guarantee to set the result code for IoErr() to
  146.     ERROR_NO_FREE_STORE    (=103L).
  147.  
  148. o)    Memory flushers:
  149.     The following is a safe memory flush:
  150.  
  151.     AllocMem(0x7ffffff0,MEMF_PUBLIC);
  152.  
  153.     (The flush used by the "avail flush" command).
  154.  
  155.  
  156. o)     AllocMem() and context switches:
  157.  
  158.     Neither AllocMem() (nor FreeMem()) break a "Forbid" state. This is
  159.     important because it's the only way to "print" a list thru the
  160.     dos.library and other functions that is access protected 
  161.     via Forbid(). 
  162.  
  163.     The following code sequence is legal for this purpose, and should
  164.     stay legal:
  165.  
  166.     - call Forbid() first,
  167.     - make a copy of that list element by element, using AllocMem()
  168.     - call Permit().
  169.     - print the copy of the list
  170.     - deallocate the copy.
  171.  
  172.     Running into a Wait(), like using a semaphore for access protection
  173.     of the memory list memory would be fatal here.
  174.  
  175.  
  176. o)     AllocMem,FreeMem,AllocAbs and interrupts:
  177.  
  178.     NONE of these functions can be called from interrupts or in the
  179.     supervisor mode.
  180.  
  181.     Remember, however, that "input handlers" of the input.device
  182.     are not run as interrupts but in the context of the input.device
  183.     task, even though they are build on top of an interrupt structure.
  184.     Thus, calling AllocMem() here to make a copy of an input event IS
  185.     LEGAL.
  186.  
  187. _____________________________________________________________________________
  188.  
  189. Usage of stack for storing: (Or, how to allocate memory without allocating it)
  190.  
  191. It's a somewhat vague point whether the stack can be used for storing
  192. system/Os structures or for passing structures to other tasks. The following
  193. paragraph is my own interpretation of this technique and should be used with
  194. some care:
  195.  
  196. The CURRENT Os implementation allows this technique. The stack is allocated
  197. with the MEMF_PUBLIC flag set, AND MUST BE ALLOCATED THIS WAY. This is simply
  198. due to the fact that the memory for the stack is allocated by the task that
  199. creates a new process, and not by the new process itself. Since the AmigaOs
  200. doesn't know the unix fork() style of creating new processes, this is the
  201. only way of allocating the stack for the new process anyways. Thus, the stack
  202. is kept in memory that is passed across task boundaries and must be,
  203. therefore, public. Thus, it can be used for storing Os structures and for
  204. passing data accross processes. It's furthermore common practice to use
  205. the stack to pass "taglists" to Os functions that might be read by a 
  206. different process, and even to keep complete Os structures on the stack, as
  207. done by some CBM shell commands, routines in the dos.library and others. 
  208. (However, see the note below about how strict CBM/AI read their own 
  209.  design rules!)
  210.  
  211.  
  212. HOWEVER, Ralph Babel writes in "The Amiga Guru Book" (2nd ed., 1993):
  213.  
  214. "The stack is private memory ... and should not be considered MEMF_CHIP
  215. or MEMF_PUBLIC, nor should it be used for storing system structures or code.
  216. The latter is important, since there is no guarantee that the stack is
  217. aligned to an even address, as these processors also allow nonbyte data
  218. acesses from any base address, although opcodes must still be word aligned."
  219.  
  220. I do not agree in this point with Ralph except that the stack is indeed
  221. usually not MEMF_CHIP and shouldn't be considered to be. Storing code on
  222. the stack is truely considered "higher magic" and should be avoided. 
  223. (Also see above for caveats IF YOU ABSOLUTELY HAVE to do this.)
  224.  
  225. However, I would suppose that stack memory is always MEMF_PUBLIC for 
  226. reasons stated above, and it's always word aligned since the MC68K keeps 
  227. track of this themselfes unless you really attempt to screw the stack up. 
  228. Normal usage of stack does not break this alignment as even a
  229.  
  230.     move.b d0,-(a7)
  231.  
  232. instruction will decrement the stack pointer BY TWO BYTES, NOT BY ONE.
  233. This is one of the lesser known features of the MC68K series, indeed, and
  234. goes for all processors, from the MC68000 to the MC68060.
  235.  
  236. Citing Motorola's "Programmer's Reference Manual" M68000PM/AD Rev.1, 
  237. Page 2-28:
  238.  
  239. "To keep data on the system stack aligned for maximum efficiency, the active
  240. stack pointer is automatically decremented or incremented by two for all 
  241. byte-size operands moved to or from the stack."
  242.  
  243. You should, however, still remember that you must align stack memory
  244. to four byte boundaries by hand. The following code snipped shows how to
  245. reserve 256 bytes of stack aligned to a longword boundary:
  246.  
  247.     lea -$104(a7),a7    ;reserve 256 bytes plus 2+2 for alignment.
  248.                 ;we use the extra two bytes to keep
  249.                 ;a possible long word alignment of the 
  250.                 ;stack and to avoid speed penalties for
  251.                 ;the more advanced processors. If you write
  252.                 ;your own routines, you should always allocate
  253.                 ;stack memory this way since the Os always
  254.                 ;generates tasks with the stack pointer
  255.                 ;aligned to four byte boundaries.
  256.  
  257.     move.l a7,d0
  258.     addq.l #2,d0        ;round up
  259.     and.b #$fc,d0        ;to next four byte boundary
  260.     move.l d0,a0        ;pass pointer in a0
  261.  
  262. However, most C compilers are not smart enough to for this technique. Even
  263. AI fall into that pithole when writing the "List" and "Dir" commands. Both
  264. don't align DOS structures to long words correctly. (Urgh!) But since a 
  265. similar code sequence is used sucessfully by the "DoPkt()" routine 
  266. inside the dos library, I would still say that using stack for Os structures 
  267. is legal and continues to stay legal. Allocating each tiny structure from
  268. the stack would create a huge overhead and would fragmentate memory a lot.
  269.  
  270. However, as I said, this is a somewhat vague point, you don't have to
  271. agree with me and I'm open for a discussion.
  272. _____________________________________________________________________________
  273.  
  274. o)    Size of deallocation:
  275.     Deallocation size MUST MATCH ALLOCATION SIZE PRECISELY. It is 
  276.     BY NO MEANS ALLOWED to
  277.  
  278.     - round the size because the rounding algorithm of the operating
  279.       system might change in future to support special hardware
  280.       (e.g. PowerPC cache lines which are 32 bytes wide)
  281.  
  282.  
  283.     Now to another rule that hasn't been formulated in the RKRMs:
  284.  
  285.     - free a partial memory block, i.e. parts of an array.
  286.       THIS IS DEFINITELY ILLEGAL, NO EXCEPTIONS, NO EXCUSES.
  287.       Free ALL OR NOTHING.
  288.  
  289.     Freeing a partial part of a memory block requires knowledge of
  290.     the alignment rules of the Os and may break code if these rules
  291.     change in future versions.
  292.  
  293.     I would therefore strongly recommend NOT to use this technique.
  294.  
  295.  
  296. o)    Access to deallocated memory:
  297.     Do not touch deallocated memory. If it's gone it's gone and you're
  298.     no longer allowed to use it, address it, read it or write data to
  299.     it. Another task might want it.
  300.  
  301.  
  302.     A tiny exception that hasn't been formulated in the RKRMs, but is
  303.     unfortunately widely used:
  304.  
  305.     Deallocation of memory WITHIN a Forbid()/Permit() pair. The memory,
  306.     EXCEPT FOR THE FIRST EIGHT BYTES WHICH ARE USED FOR ADMINISTRATION,
  307.     is guaranteed to stay unmodified and ready for use as long as
  308.     the multitasking is disabled. Running into a Wait(), directly or
  309.     indirectly, will break the Forbid() state and will therefore make
  310.     the memory unusable.
  311.  
  312.     Be warned! Even though this access is sort of legal, hence 
  313.     tolerated by MungWall, MemSniff, PoolMem and others, it's IMHO 
  314.     still ugly and therefore highly discouraged. One of the very few 
  315.     exceptions where this feature might be helpful is the following 
  316.     code segment that unloads the segment of a load- and stay resident 
  317.     program:
  318.  
  319.     move.l SysBase(a4),a6
  320.     jsr _LVOForbid(a6)
  321.     move.l DOSBase(a4),a6
  322.     move.l Segment(a4),d1
  323.     jsr _LVOUnloadSeg(a6)        ;Unload own code
  324.     move.l a6,a1            ;THIS CODE STAYS LEGAL because
  325.     move.l SysBase(a4),a6        ;of the Forbid()
  326.     jsr CloseLibrary(a6)        ;close dos
  327.     moveq #0,d0
  328.     rts                ;exit.
  329.  
  330.     Note that you must definitely positively sure that the segment
  331.     is not an overlayed segment because UnloadSeg() WILL break the
  332.     Forbid() state in this case. However, this doesn't work for
  333.     load- and stay-resident programs anyways.
  334.  
  335.  
  336. o)    Chip memory and blitter access.
  337.     The custom "blitter logic" uses DMA and accesses the chip memory
  338.     independent of the CPU. If you use a temporary buffer for the blitter,
  339.     make sure the blitter does no longer access this buffer before you
  340.     deallocate it. To be on the safe side, call WaitBlit() before de-
  341.     allocating memory that has been used as blitter buffer.
  342.  
  343. o)    Memory and hardware DMA access.
  344.     Modern hard disk interfaces might access memory by DMA, parallel
  345.     to the CPU. If you're planning to use this hardware DMA directly
  346.     because you're writing a device driver for this hardware, be
  347.     prepared to flush the CPU caches properly. Especially, call
  348.     
  349.     CachePreDMA(...)
  350.         prior the DMA operation
  351.  
  352.     CachePostDMA(...)
  353.         afterwards.
  354.  
  355.     Check the autodocs for details about these functions and their
  356.     parameters.
  357.  
  358.  
  359. o)    Return value:
  360.     FreeMem() DOES NOT return any useful value, nor does it set any
  361.     condition codes.
  362.  
  363. ______________________________________________________________________________
  364.  
  365. AllocAbs and other wierdos:
  366.  
  367. AllocAbs is for specialized usage of allocating memory from a predefined
  368. location. DO NOT USE IT WITHOUT GOOD REASON.
  369.  
  370. o)    Range of allocated memory:
  371.     AllocAbs performs some rounding. Be prepared that the memory block
  372.     you get is not identical to the memory block you requested.
  373.     However, IF the memory allocation could be satisfied, the requested
  374.     memory block is guaranteed to be contained in the returned memory
  375.     block.
  376.     Feel prepared that the memory request cannot be satisfied because
  377.     the requested memory is already in use by a different task. 
  378.  
  379.     AllocAbs() returns NULL in this case. You've to check for this
  380.     explicitly! It does NOT set any condition codes.
  381.  
  382.     AllocAbs() WILL NOT set the ERROR_NO_FREE_STORE return code for
  383.     IoErr().
  384.  
  385. o)    Contents of allocated memory:
  386.     Do not make any asumptions about the contents of the 
  387.     allocated memory block. The OS uses parts of the free memory blocks
  388.     for administratory purposes and might have been trashed parts of
  389.     memory block. 
  390.  
  391.     That means especially for reset resident programs - whose memory is
  392.     allocated this way by the exec KickMemPtr mechanism - that the 
  393.     first eight bytes will be trashed. Be prepared for that feature!
  394.  
  395. o)    Deallocation of AllocAbs()-ed memory:
  396.  
  397.     To be sure that the allocated memory is really deallocated completely,
  398.     call FreeMem with the memory address and size you REQUESTED, NOT
  399.     with the return value of AllocAbs(). This might sound strange indeed,
  400.     but the FreeMem() logic performs the same rounding of size and address
  401.     as the AllocAbs() logic. If, however, you pass in a different address,
  402.     as the return value instead of the requested address, it is not
  403.     guaranteed that really all memory is deallocated.
  404.  
  405.     A tiny example might be helpful (asuming the the current rounding
  406.     algorithm):
  407.  
  408.     AllocAbs(0x07,0x300007);
  409.  
  410.     allocates 16 bytes and returns 0x300000. Calling now
  411.  
  412.     FreeMem(0x300000,0x07);
  413.  
  414.     will only free EIGHT bytes starting from 0x300000 instead of
  415.     16 bytes. However,
  416.  
  417.     FreeMem(0x300007,0x07);
  418.  
  419.     will work as required.
  420.  
  421.  
  422.     I'm sorry to say that the kludge documented in the last revision 
  423.     of this file failed for the same reason; this has been fixed.
  424.     
  425.  
  426. o)    Using AllocAbs() for aligned memory allocation:
  427.     The following code segment is a kludge for allocating memory aligned 
  428.     to a boundary:
  429.  
  430. void *AllocAligned(ULONG bytesize,ULONG attributes,ULONG alignment)
  431. {
  432. UBYTE *mem,*res;
  433.  
  434.     alignment--;
  435.     if (mem=AllocMem(bytesize+alignment,attributes & (~MEMF_CLEAR))) {
  436.         Forbid();
  437.         FreeMem(mem,bytesize+alignment);
  438.         mem = (mem + alignment)&(~alignment);
  439.         res = AllocAbs(bytesize,mem);
  440.         Permit();
  441.         if (res) {
  442.             if (attributes & MEMF_CLEAR)
  443.                 memset(mem,0,bytesize);
  444.         } else mem = NULL;
  445.     }
  446.     return mem;
  447. }
  448.  
  449.     I.e, call this routine with "aligment" set to 16 for an alignment
  450.     to a sixteen byte boundary.
  451.     Calling this routine with anything but a power of two for the
  452.     alignment doesn't make much sense and is illegal.
  453.  
  454.     Note that the memory is cleared MANUALLY if MEMF_CLEAR is set.
  455.     This MUST be done since AllocAbs() does not guarantee the
  456.     contents of the memory, even if the former AllocMem() already
  457.     cleared the memory.
  458.  
  459. _____________________________________________________________________________
  460.  
  461. Any program that obeys these rules won't have *any* problems with PoolMem!
  462. _____________________________________________________________________________
  463.  
  464. Debugging tools (memory related):
  465.  
  466.  
  467. The following two debugging tools are "official" AI tools and should be used
  468. by any serious developer:
  469.  
  470. -Enforcer:    Detects memory accesses to the vector base and to unmapped
  471.         memory regions.
  472.  
  473. -MungWall:    Detects a lot of illegal accesses as in the list above,
  474.         as failing to initialize memory properly, accessing de-
  475.         allocated memory and others. However, it *could* do more.
  476.  
  477. -SegTracker:    Keeps program names together with their loaded segments
  478.         for easy identification of code.
  479.  
  480.  
  481. Even a program that runs without problems with these tools is not
  482. necessarely bug free!
  483.  
  484. The use of the following debugging tools is highly recommended:
  485.  
  486. -PatchWork:    (by Richard Körber)
  487.         Detects invalid parameters to Os calls.
  488.  
  489. I would also recommend the following combination: Since this is my own
  490. stuff, I can't be very objective. You might want to check them out....
  491.  
  492. - COP:        (my own stuff)
  493.         Catches gurus and exceptions "on line" for straight 
  494.         forewards debugging.
  495.  
  496. - MemSniff:    Even pickier than MungWall. It detects software failures
  497.         and memory problems MungWall can't find. However, check
  498.         the documentation as this tool has its special "caveats".
  499.         It should be used in conjunction with COP since it doesn't
  500.         generate an as complete output as MungWall or Enforcer.
  501.  
  502. - SaferPatches:    Detects illegal function patches. If this one crashes with
  503.         a guru, something is wrong. For details, check the doc
  504.         of the SaferPatches archive.
  505. _____________________________________________________________________________
  506.  
  507. Another set of wierdos for the "enlighted". (-:
  508.  
  509. The following is a list of "OS features" you should be aware of if you
  510. consider writing your own memory tool. I found them when writing PoolMem,
  511. so they are here for your information. However, DO NOT USE THESE TECHNIQUES
  512. in own code.
  513.  
  514. Even though the above rules have been setup for the developer, that doesn't
  515. mean that the Os respects these rules ("Quod licet Iovi non licet bovi.").
  516.  
  517.  
  518. I found the following "OS features":
  519.  
  520.  
  521. - The FFS (all versions V37 thru V43) expect a return value of "-1" for
  522.   FreeMem(). It is said that this will be fixed.
  523.  
  524. PoolMem contains a kludge for that. MemSniff and MungWall will mess up
  525. the registers on purpose.
  526.  
  527. - The layers.libray allocates memory in large blocks, but deallocates this
  528.   large block of memory in a series of small deallocations. In other words,
  529.   it breaks up large memory blocks in smaller ones.
  530.     
  531. The current version of PoolMem respects this behaviour, not only for the 
  532. layers.library. MungWall and MemSniff include special kludges to allow this
  533. EXCLUSIVELY for the layers.library. However, THIS HAS BEEN ILLEGAL, IS 
  534. ILLEGAL AND WILL CONTINUE TO BE ILLEGAL. I hope that this mess will be 
  535. cleaned up in a future Os revision. 
  536.  
  537. - Some programs expect the Z (zero) flag of the CPU after an AllocMem() 
  538.   call to be set on failure and to be cleared if the allocation worked.
  539.   THIS IS UNDOCUMENTED.
  540.  
  541. The current version of PoolMem contains a kludge to make these programs
  542. working.
  543.  
  544. - Some Os functions allocate Os structures from the stack and pass them
  545.   to other tasks. (The DoPkt() routine is one example, but there are others).
  546.  
  547. I would still say that this is O.K., but if you don't want to follow me in
  548. this point, it's an Os bug.
  549. _____________________________________________________________________________
  550.  
  551. Thomas Richter,        April 1998
  552.